#ifndef __serialport_kenny_kjfd89fu9uew9ufe9u_kenny__
#define __serialport_kenny_kjfd89fu9uew9ufe9u_kenny__

#if defined( _WIN32 ) || defined( _WIN64 )
#	include <Windows.h>
#else
#	include <unistd.h>
#	include <sys/types.h>  
#	include <sys/stat.h>   
#	include <fcntl.h>
#	include <termios.h>
#	include <errno.h>
#endif

#include <cstdio>
#include <cstdlib>
#include <cstring>

#include <hude/aio/context.hpp>

namespace hude
{
	namespace utils
	{
		/**********************************************************************************************
		 * @brief serialport_handle_t
		 */
		struct serialport_handle_t
		{
#if defined( _WIN32 ) || defined( _WIN64 )
			typedef void* handle_t;
#else
			typedef int handle_t;
#endif

			enum : handle_t { emNULL = -1 };
		};
		
		/**********************************************************************************************
		 * @brief serialport_t
		 */
		class serialport_t :
			public hude::aio::e2e::_peer_t
		{
		public:
			typedef serialport_handle_t::handle_t handle_t;

		public:
			serialport_t();
			explicit serialport_t( const char* filename, int speed = 115200, bool block = true );
			explicit serialport_t( const char* filename, int speed, bool block, int bits,int stop,int parity );

			~serialport_t() override;

			/**
			 * *@brief   open the serial port device
			 * *@param  filename     type  const char*  serial port name
			 * *@param  block     block mode
			 * */
			bool open( const char* filename, bool block = true );

			/**
			 * *@brief  close serial port
			 * */
			void close() override;

			/**
			 * *@brief  serial port settings
		 	 * *@param  speed  type int, baud rate
		 	 * *@param  block  type unsigned char,  0xff mode block, other timeout( n * ( 1 / 10 ) sec )
			 * *@param  bits type int, bits size of data, value is 7 or 8
			 * *@param  stop type int, stop bit value is 0, 1 or 2
			 * *@param  parity  type int, verify type value is N,E,O,S
			 * */
			bool settings( int speed, unsigned char block = 1U, int bits = 8, int stop = 0, int parity = 0 );

			/**
			 * *@brief  read data
			 * *@param  buffer   type char*,  buffer of save data
			 * *@param  size     type unsigned int,  buffer lenght
			 * */
			int read( char* buffer, unsigned int size );

			inline int read( unsigned char* buffer, unsigned int size )
			{
				return this->read( (char*)buffer, size );
			}

			template< typename _type >
			int read( const _type& data ) { return read( (char*)(void*)&data, sizeof( data ) ); }

			int recv( void* ptr, size_t siz ) override { return read( (char*)ptr, (uintptr_t)siz ); }

			/**
			 * *@brief  write data
			 * *@param  buffer   type char*,  buffer of send data
			 * *@param  size     type unsigned int,  buffer lenght
			 * */
			int write( const char* buffer, unsigned int size );

			inline int write( const unsigned char* buffer, unsigned int size )
			{
				return this->write( (const char*)buffer, size );
			}

			template< typename _type >
			int write( const _type& data ) { return write( (const char*)(const void*)&data, sizeof( data ) ); }

			int send( const void* ptr, size_t siz ) override { return write( (const char*)ptr, (uintptr_t)siz ); }

			/**
			 * *@brief return handle of tty device file
			 * */
			handle_t handle() const { return file_; }
			handle_t native() const override { return file_; }
			
			/**
			 * *@brief return null handle
			 * */
			static handle_t null_handle() { return serialport_handle_t::emNULL; }
			/**
			 * *@brief return file_
			 * */
			bool is_open();
		private:
			handle_t file_;
			char name_[32];

        private:
			serialport_t(const serialport_t&);
			serialport_t& operator=( serialport_t& );
		};

	} //namespace utils

} //namespace hude

namespace hude
{
	namespace utils
	{
		
		//--------------------------------------------------------------------------------
#if defined( _WIN32 ) || defined( _WIN64 )
#	define _BR( br ) CBR_##br
#else
#	define _BR( br ) B##br
#endif
		//--------------------------------------------------------------------------------
		static const int speed_arr[] = { _BR(115200), _BR(38400), _BR(19200), _BR(9600),
			_BR(4800), _BR(2400), _BR(1200), _BR(300), _BR(38400), _BR(19200),
			_BR(9600), _BR(4800), _BR(2400), _BR(1200), _BR(300) };
#undef _BR

		//--------------------------------------------------------------------------------
		static const int name_arr[] = { 115200, 38400,  19200,  9600,  4800,  2400,  1200,  300, 38400, 19200,  9600, 4800, 2400, 1200,  300 };
		
		/**********************************************************************************************
		 * @brief serialport_base_t
		 */
#if defined( _WIN32 ) || defined( _WIN64 )

		struct serialport_base_t
		{
			typedef serialport_handle_t::handle_t handle_t;

			//--------------------------------------------------------------------------------
			static bool set_speed( serialport_handle_t::handle_t file, int speed )
			{
				_DCB dcb;

				if( FALSE == GetCommState( file, &dcb ) )
					return false;
				
				for( int i = 0; i < sizeof( speed_arr ) / sizeof( int ); i++)
				{
					if( speed == name_arr[i] )
					{
						dcb.BaudRate = speed_arr[i];

						return FALSE != SetCommState( file, &dcb );
					}
				}

				return false;
			}

			//--------------------------------------------------------------------------------
			static bool set_parity( serialport_handle_t::handle_t& file, bool, int databits, int stopbits, int parity )
			{
				_DCB dcb;

				if( FALSE == GetCommState( file, &dcb ) )
					return false;
				
				dcb.ByteSize = (BYTE)databits;
				dcb.fParity =TRUE;
				dcb.fNull = FALSE;
				dcb.Parity = (BYTE)parity;
				dcb.StopBits = (BYTE)stopbits;

				if( FALSE == SetCommState( file, &dcb ) )
					return false;
				
				PurgeComm( file, PURGE_RXABORT | PURGE_TXCLEAR | PURGE_RXCLEAR | PURGE_TXABORT );

				return true;
			}

			//--------------------------------------------------------------------------------
			static bool open( const char* filename, serialport_handle_t::handle_t& file, bool block = true )
			{
				file = CreateFile( filename, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, block? 0 : (FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED), NULL);
				
				if( file == serialport_handle_t::emNULL ) return false;

				SetupComm( file, 0xff, 0xff );
				
				COMMTIMEOUTS ct;

				ct.ReadIntervalTimeout = MAXDWORD;

				ct.ReadTotalTimeoutConstant = block? 3 : 0;
				ct.ReadTotalTimeoutMultiplier = block? 3 : 0;

				ct.WriteTotalTimeoutMultiplier = 30;
				ct.WriteTotalTimeoutConstant = 30;

				if( ! block )
				{
					u_long yes = 1;

					if( SOCKET_ERROR == ioctlsocket( s, FIONBIO, &yes ) ) return false;
				}

				return FALSE != SetCommTimeouts( file, &ct );
			}

			//--------------------------------------------------------------------------------
			static void close( serialport_handle_t::handle_t & file )
			{
				CloseHandle( file );
				file = serialport_handle_t::emNULL;
			}
			

			//--------------------------------------------------------------------------------
			static int read( serialport_handle_t::handle_t file, char* buffer, unsigned int size, void* ov = nullptr )
			{
				unsigned int rs = 0;

				BOOL ret = ReadFile( file, buffer, size, (DWORD*)&rs, (OVERLAPPED*)ov );

				return ( FALSE == ret )? 0 : (int)rs;
			}
			

			//--------------------------------------------------------------------------------
			static int write( serialport_handle_t::handle_t file, const char* buffer, unsigned int size, void* ov = nullptr )
			{
				unsigned int ws = 0;

				BOOL ret = WriteFile( file, buffer, size, (DWORD*)&ws, (OVERLAPPED*)ov );
				
				return ( FALSE == ret )? 0 : ws;
			}
		};
#else
		struct serialport_base_t
		{
			typedef serialport_handle_t::handle_t handle_t;

			//--------------------------------------------------------------------------------
			static bool set_speed( serialport_handle_t::handle_t fd, int speed )
			{
				unsigned int i;
				struct termios opt;    

				if( tcgetattr( fd, &opt ) != 0 )
				{
					return false;
				}

				for(i = 0; i < sizeof(speed_arr) / sizeof(int); i++)
				{
					if(speed == name_arr[i])
					{
						tcflush(fd, TCIOFLUSH);

						cfsetispeed(&opt, speed_arr[i]);
						cfsetospeed(&opt, speed_arr[i]);

						if(tcsetattr(fd, TCSANOW, &opt) != 0)
						{
							return false;
						}
						
						tcflush(fd, TCIOFLUSH);

						break;
					}
				}

				return true;
			}

			//--------------------------------------------------------------------------------
			static bool set_parity( serialport_handle_t::handle_t fd, unsigned char block, int databits, int stopbits, int parity )
			{
				struct termios opt;

				if(tcgetattr(fd, &opt) != 0)
				{
					return false;
				}

				opt.c_cflag |= (CLOCAL | CREAD);

				switch(databits)        //set bit lenght of data
				{
					case 7:
						opt.c_cflag &= ~CSIZE;
						opt.c_cflag |= CS7;
						break;
					case 8:
						opt.c_cflag &= ~CSIZE;
						opt.c_cflag |= CS8;
						break;
					default:
						return false;
				}

				switch(parity)            //parity bit
				{
					case 0:
					case 'n':
					case 'N':
						opt.c_cflag &= ~PARENB;        //clean parity
						opt.c_iflag &= ~INPCK;        //enable parity checking
						break;
					case 'o':
					case 'O':
						opt.c_cflag |= PARENB;        //enable parity
						opt.c_cflag |= PARODD;
						opt.c_iflag |= INPCK;         //disable parity checking
						break;
					case 'e':
					case 'E':
						opt.c_cflag |= PARENB;        //enable parity
						opt.c_cflag &= ~PARODD;
						opt.c_iflag |= INPCK;         //disable pairty checking
						break;
					case 's':
					case 'S':
						opt.c_cflag &= ~PARENB;        //clean parity
						opt.c_cflag &= ~CSTOPB;
						opt.c_iflag |= INPCK;          //disable pairty checking
						break;
					default:
						return false;    
				}

				switch(stopbits)        //set stop bits
				{
					case 0:
						opt.c_cflag &= ~CSTOPB;
						break;
					case 1:
						opt.c_cflag &= ~CSTOPB;
						break;
					case 2:
						opt.c_cflag |= CSTOPB;
						break;
					default:
						return false;
				}

				opt.c_cflag |= (CLOCAL | CREAD);
				opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
				opt.c_oflag &= ~OPOST;
				opt.c_oflag &= ~(ONLCR | OCRNL);
				opt.c_iflag &= ~(ICRNL | INLCR);
				opt.c_iflag &= ~(IXON | IXOFF | IXANY);

				tcflush(fd, TCIFLUSH);

				opt.c_cc[VTIME] = block;        //timeout n * (1 / 10) sec
				opt.c_cc[VMIN] = ( block == 0xff )? 1 : 0;

				if(tcsetattr(fd, TCSANOW, &opt) != 0)
				{
					return false;
				}

				return true;
			}

			//--------------------------------------------------------------------------------
			static bool open( const char* filename, serialport_handle_t::handle_t& file, bool block = true )
			{
				file = ::open( filename, O_RDWR | O_NOCTTY | (block? 0 : O_NONBLOCK) );

				if( serialport_handle_t::emNULL != file && ! block )
				{
					int flags;

					if ( -1 == ( flags = fcntl( file, F_GETFL ) ) )
					{
						return -1;
					}

					if ( -1 == fcntl( file, F_SETFL, flags | O_NONBLOCK ) )
					{
						return -1;
					}
				}

				return serialport_handle_t::emNULL != file;
			}

			//--------------------------------------------------------------------------------
			static void close( serialport_handle_t::handle_t & file )
			{
				if( file != serialport_handle_t::emNULL ) { ::close( file ); file = serialport_handle_t::emNULL; }
			}

			//--------------------------------------------------------------------------------
			static int read( serialport_handle_t::handle_t file, char* buffer, unsigned int size, void* = nullptr )
			{
				if( file == serialport_handle_t::emNULL ) return 0;

				int ret = ::read( file, buffer, size );

				return ret;
			}

			//--------------------------------------------------------------------------------
			static int write( serialport_handle_t::handle_t file, const char* buffer, unsigned int size, void* = nullptr )
			{
				if( file == serialport_handle_t::emNULL ) return 0;

				int ret = ::write( file, buffer, size );

				return ret;
			}
		};
#endif

		//--------------------------------------------------------------------------------
		inline bool serialport_t::open( const char* filename, bool block )
		{
			if( file_ != serialport_handle_t::emNULL )
				serialport_base_t::close( file_ );

			if( filename == nullptr ) return false;

			strncpy( name_, filename, sizeof( name_ ) - 1 );

			return serialport_base_t::open( filename, file_, block );
		}


		//--------------------------------------------------------------------------------
		inline void serialport_t::close()
		{
			if( file_ == serialport_handle_t::emNULL )
				return;

			serialport_base_t::close( file_ );
			
			name_[0] = '\0';
		}

		//--------------------------------------------------------------------------------
		inline bool serialport_t::is_open()
		{
			if (file_ == serialport_handle_t::emNULL)
				return false;
			else
				return true;
		}


		//--------------------------------------------------------------------------------
		inline serialport_t::serialport_t() : file_( serialport_handle_t::emNULL ) {}

		//--------------------------------------------------------------------------------
		inline serialport_t::serialport_t(const serialport_t& rhs)
		{
			file_= rhs.file_;
		}

		//--------------------------------------------------------------------------------
		inline serialport_t& serialport_t::operator=(serialport_t& ths)
		{
			if (&ths == this)
			{
				return *this;
			}

			this->file_ = ths.file_;

			return *this;
		}
		//--------------------------------------------------------------------------------
		inline serialport_t::serialport_t( const char* filename, int speed, bool block ) : file_( serialport_handle_t::emNULL )
		{
			this->open( filename );
			settings( speed, block? 0xff : 0, 8, 1, 'N' );
		}

		//--------------------------------------------------------------------------------
		inline serialport_t::serialport_t( const char* filename, int speed, bool block,
			int bits,int stop,int parity ) : file_( serialport_handle_t::emNULL )
		{
			this->open( filename );
			settings( speed, block? 0xff : 0, bits, stop, parity );
		}

		//--------------------------------------------------------------------------------
		inline serialport_t::~serialport_t()
		{
			this->close();
		}

		//--------------------------------------------------------------------------------
		inline bool serialport_t::settings( int speed, unsigned char block, int bits, int stop, int parity )
		{
			if( ! serialport_base_t::set_speed( file_, speed ) ) return false;
			if( ! serialport_base_t::set_parity( file_, block, bits, stop, parity ) ) return false;

			return true;
		}

		//--------------------------------------------------------------------------------
		inline int serialport_t::read( char* buffer, unsigned int size )
		{
			return serialport_base_t::read( file_, buffer, size );
		}

		//--------------------------------------------------------------------------------
		inline int serialport_t::write( const char* buffer, unsigned int size )
		{
			return serialport_base_t::write( file_, buffer, size );
		}
		
	} //namespace utils
} //namespace hude


#endif //__serialport_kenny_kjfd89fu9uew9ufe9u_kenny__

